/*
  Fair Share Scheduler
  This schedulers aims to allow to run all runnable processes (in the ready 
  queue) at least once during an adjustable time period. The time slice assigned
  to each process depends on the number of process groups and number of
  processes running in them. Scheduling is done fairly as described in a
  previous lab description of fair-share group policy.

  When a process becomes ready, it affects slice allocated for the processes
  within its group. When it is queued to one of the Waiting or Terminated
  queues, it does not affect the same anymore.

  Author(s):
    David Dumas - ddumas@nmt.edu
    Hakan Akkan - hkn@hakanakkan.com
*/

#include <stdio.h>
#include <stdlib.h>
#include <pm.h>
#include <sched.h>

// PIE_SIZE defines the number of cycles that we want all processes to be
// scheduled at least once within that cycles.
#define PIE_SIZE 1000

struct fs_sched_state state;

struct fs_sched_state *get_sched_state(){
	return &state;
}

int get_sched_size(){
	int out = 0;
	out += sizeof(struct fs_sched_state) * 1;
	return out;
}

void init_sched(void){
	int i = MAX_GROUPS;
	state.total_cycles = PIE_SIZE;
	state.current_cycle = 0; // Just started
	state.n_active_groups = 0;
	while(i) state.group_ready_proc_counts[--i] = 0;
}

void init_sched_info(struct sched_info* si){
	si->group_no = -1;
	si->running_since = 0;
}
// Returns how many prcesses we started
int go(){
	int index = 0;
	int n_started = 0;
	struct process *p_to_run;
	//check, and make sure each core has a process
	for(index = 0; index < NUM_PROC; index++)
	{
		if(running_list[index] == NULL)
		{
			// pull the first process from the ready queue
			//p_to_run = ready_queue.head;
			p_to_run = peak(&ready_queue);
			if(p_to_run == NULL)
				// nothing is waiting.
				return n_started;
			// There is an empty processor and a process in the ready queue.
			// Let's run it.
			queue_to_running(p_to_run);
			p_to_run->sched.running_since = state.current_cycle;
			n_started++;
		}
	}
	//return how many processes we started
	return n_started;
}

inline void calculate_quantum_and_cycles_left(struct process* p,
																								int *q, int *left){
		*q = state.slice_group /
								state.group_ready_proc_counts[p->sched.group_no];
		*left = *q - (state.current_cycle - p->sched.running_since);
}

// This will simulate the action of running a process until it consumes
// all its time on its CPU. We pick the process which is closest to completing
// its time. Returns the pid of the process we just preempted or 0 if
// we could not do so.
int end_of_quantum(){

	// Find how many cycles we need to shift the time so that a process
	// uses up all its current quantum. The minimum value of
	//    quantum - (current_cycle - running_since)
	// will determine who will be swapped out.
	int core;
	int quantum;
	int cycles_left; // to be preempted
	int shortest_left = state.total_cycles + 1;
	int shortest_core_no = -1; // What processor will be emptied
	struct process* current;

	for(core = 0; core < NUM_PROC; core++){
		current = get_process_from_processor(core);
		if(!current) continue; // That core was empty
		// Calculate the quantum for this process
		calculate_quantum_and_cycles_left(current,&quantum,&cycles_left);
		
		if(cycles_left < shortest_left) {
			shortest_left = cycles_left;
			shortest_core_no = core;
		}
	}

	if(shortest_core_no == -1) return 0; // Nothing to preempt

	//printf("shortest was %d\n",shortest_left);
	// Shift the time!
	if(shortest_left > 0) {
		state.current_cycle += shortest_left;
	}

	// Update stats of all running processes
	for(core = 0; core < NUM_PROC; core++){
		current = get_process_from_processor(core);
		if(!current) continue; // That core was empty
		// Calculate the quantum for this process
		current->stats.time_run += shortest_left;
	}


	// Do preempt!
	current = get_process_from_processor(shortest_core_no); // to preempt
	current->sched.running_since = 0;
	queue_to_ready(current);
	return current->pid;
	/*
	int index = 0;
	//I've assumed that the process running longest is on core 0, but we'll check
	//in the loop
	int done_proc = 0;//the processor with the process that should run out of
	//time at the end of this quantum
	int longest_time_run = running_list[0]->stats.time_run;//the longest running
	//time of all running processes
	int time_passed = 0;//the amount of time that has passed during this quantum
	
	//this loop finds the process that has been running the longest
	for(index = 1; index < NUM_PROC; index++)
	{
		if(running_list[index]->stats.time_run > longest_time_run)
		{
			done_proc = index;
		}
	}
	
	//calculate the amount of time that has passed
	time_passed = RUN_TICKS / n_running_p - longest_time_run;
	
	//the time that each process has run is incremented
	for(index = 0; index < NUM_PROC; index++)
	{
		if(running_list[index] != NULL)
			running_list[index]->stats.time_run += time_passed;
	}
	
	//we send the process that ran out of time to the ready_queue
	queue_to_ready(running_list[done_proc]);
	*/
}

int add_ready_proc_to_group(struct process* p){
	int gr_no = p->sched.group_no;
	//printf("Adding ready proc (%d) to group %d\n",p->pid,gr_no);
	state.group_ready_proc_counts[gr_no]++;
	// If this is a new group (was empty), then increment the n_active_groups.
	if(state.group_ready_proc_counts[gr_no] == 1){
		// There is a new group. Update values accordingly
		//printf("Group %d became active\n",gr_no);
		state.n_active_groups++;
		state.slice_group = state.total_cycles / state.n_active_groups;
	}
	return 0;
}

// Decrements group's proc counter by 1. This function assumes that
// process was either in the ready queue or was running.
void remove_proc_from_ready_group(struct process* p){
	int gr_no = p->sched.group_no;
	//printf("Removing proc (%d) from group %d\n",p->pid,gr_no);
	state.group_ready_proc_counts[gr_no]--;
	if(state.group_ready_proc_counts[gr_no] == 0){
		//printf("Group %d became inactive\n",gr_no);
		if(--state.n_active_groups)
			state.slice_group = state.total_cycles / state.n_active_groups;			
	}
}
